﻿Imports System.Net
Imports System.Net.Sockets
Imports Framework.IO

Public Class Node
    Private mClientCommands As TcpClient
    Private mClientData As TcpClient
    Private mClientDataStream As ThrottledNetworkStream

    Private mCommandReadThread As System.Threading.Thread

    Private mDataSendThread As System.Threading.Thread
    Private mDataReadThread As System.Threading.Thread

    Private mMyASymCryptoProvider As System.Security.Cryptography.RSACryptoServiceProvider
    Private mBuddyASymCryptoProvider As System.Security.Cryptography.RSACryptoServiceProvider

    Private mCommandSendCryptoProvider As CryptoProviders.B2B1 ' System.Security.Cryptography.RijndaelManaged
    Private mCommandReceiveCryptoProvider As CryptoProviders.B2B1 ' System.Security.Cryptography.RijndaelManaged

    Private mDataSendCryptoProvider As CryptoProviders.B2B1 ' System.Security.Cryptography.RijndaelManaged
    Private mDataReceiveCryptoProvider As CryptoProviders.B2B1 ' System.Security.Cryptography.RijndaelManaged

    Private mBuddy As Entities.Buddy
    Private mIsOnline As Boolean
    Private mOneTimeDataChannelPassCode As String
    Private mBuddyKeyMaster As Framework.Security.KeyMaster = Framework.Security.KeyMaster.Anonymous

    Private mVersion As String
    Public Property Version() As String
        Get
            Return mVersion
        End Get
        Friend Set(ByVal value As String)
            mVersion = value
        End Set
    End Property

    Public Property Buddy() As Entities.Buddy
        Get
            Return mBuddy
        End Get
        Friend Set(ByVal value As Entities.Buddy)
            If Not mBuddy Is value Then
                mBuddy = value
                If mBuddy IsNot Nothing Then
                    mBuddyKeyMaster = Framework.Security.KeyMaster.GetByBuddyId(mBuddy.Id)
                Else
                    mBuddyKeyMaster = Framework.Security.KeyMaster.Anonymous
                End If
            End If
        End Set
    End Property
    Public ReadOnly Property BuddyId() As Integer
        Get
            If mBuddy Is Nothing Then
                Return -1
            Else
                Return mBuddy.Id
            End If
        End Get
    End Property
    Public ReadOnly Property BuddyKeyMaster() As Framework.Security.KeyMaster
        Get
            Return mBuddyKeyMaster
        End Get
    End Property
    Public ReadOnly Property Name() As String
        Get
            If mBuddy Is Nothing Then
                Return mNetworkId
            Else
                Return mBuddy.NameLocal
            End If
        End Get
    End Property
    Friend Property OneTimeDataChannelPassCode() As String
        Get
            Return mOneTimeDataChannelPassCode
        End Get
        Set(ByVal value As String)
            mOneTimeDataChannelPassCode = value
        End Set
    End Property

    Public ReadOnly Property IsOnline() As Boolean
        Get
            Return mIsOnline
        End Get
    End Property
    Public ReadOnly Property IsDisconnecting() As Boolean
        Get
            Return mIsDisconnecting
        End Get
    End Property

    Public Sub StartPairUp(ByVal host As String, ByVal port As Integer, ByVal buddyNr As Integer, ByVal pinCode As String)
        mNetworkId = host & ":" & port
        Dim clsPairUpInfo As New PairUpInfo
        clsPairUpInfo.Host = host
        clsPairUpInfo.Port = port
        clsPairUpInfo.BuddyNr = buddyNr
        clsPairUpInfo.PinCode = pinCode

        Dim clsIP As System.Net.IPHostEntry = System.Net.Dns.GetHostEntry(host)
        If clsIP.AddressList.Count = 0 Then
            System.Windows.Forms.MessageBox.Show("Could not find adress for {0}!", "Dns", Windows.Forms.MessageBoxButtons.OK, Windows.Forms.MessageBoxIcon.Exclamation)
            Return
        End If
        Manager.OnNodeLogEvent(Me, String.Format("Starting to connect to {0}...", clsIP.AddressList(0).ToString))

        mCommandReadThread = New System.Threading.Thread(AddressOf Me.WorkerPairUp)
        mCommandReadThread.Name = "NetNode.Node client command read thread for " & mNetworkId
        mCommandReadThread.Priority = System.Threading.ThreadPriority.BelowNormal
        mCommandReadThread.IsBackground = True
        mCommandReadThread.Start(clsPairUpInfo)
    End Sub
    Public Sub StartConnect(ByVal isAutomatedRetry As Boolean)
        Dim clsIP As System.Net.IPHostEntry = System.Net.Dns.GetHostEntry(mBuddy.Host)
        If clsIP.AddressList.Count = 0 Then
            Dim strMessage As String = String.Format("Could not find adress for {0}!", mBuddy.Host)
            If isAutomatedRetry Then
                Manager.OnNodeLogEvent(Me, strMessage)
            Else
                System.Windows.Forms.MessageBox.Show(strMessage, "Dns", Windows.Forms.MessageBoxButtons.OK, Windows.Forms.MessageBoxIcon.Exclamation)
            End If
            Return
        End If
        Manager.OnNodeLogEvent(Me, String.Format("Starting to connect to {0}...", clsIP.AddressList(0).ToString))

        mNetworkId = mBuddy.Host & ":" & mBuddy.Port
        mCommandReadThread = New System.Threading.Thread(AddressOf Me.WorkerClient)
        mCommandReadThread.Name = "NetNode.Node client command read thread for " & mBuddy.Host & ":" & mBuddy.Port
        mCommandReadThread.Priority = System.Threading.ThreadPriority.BelowNormal
        mCommandReadThread.IsBackground = True
        mCommandReadThread.Start(isAutomatedRetry)
    End Sub

    Private Sub WorkerClient(ByVal isAutomatedRetry As Object)
        Dim blnIsAutomatedRetry As Boolean = False
        If TypeOf isAutomatedRetry Is Boolean Then blnIsAutomatedRetry = CBool(isAutomatedRetry)
        Try
            mClientCommands = New TcpClient

            Manager.OnNodeStatusChanged(Me)

            mClientCommands.Connect(mBuddy.Host, mBuddy.Port)

            SendCommandLine(mClientCommands, "TYPE:COMMANDCHANNEL")

            If Not NegotiateEncryption(False) Then Return

            Dim clsArgs As New CommandBlocks.Authenticate.Request
            clsArgs.UniqueId = Framework.Net.Settings.UniqueId
            clsArgs.PassCode = mBuddy.PassCode_LoginAtBuddy
            clsArgs.Email = Framework.Net.Settings.Email
            clsArgs.Name = Framework.Net.Settings.Nickname
            clsArgs.Version = System.Windows.Forms.Application.ProductVersion

            PostCommandMessage("AUTHENTICATE", Framework.Serialization.ToXmlBytes(clsArgs))

            Dim clsReplyCommand As MessageBlock = ReadMessageBlock(mClientCommands, 30)
            If clsReplyCommand.Type <> "AUTHENTICATEREPLY" Then Return
            If Not CommandBlocks.Authenticate.HandleReply(Me, clsReplyCommand) Then Return
            If Not blnIsAutomatedRetry Then Manager.OnNodeLogEvent(Me, String.Format("Authenticated with buddy {0} complete", mBuddy.NameLocal))

            If Not CreateAndConnectDataChannel() Then Return

            SendCommandLine("ENTERCOMMANDLOOP")

            'start data loop
            StartDataLoop()
            'now, go into the command loop
            CommandLoop()
        Catch ex As System.Threading.ThreadAbortException
        Catch ex As Exception
            If Not blnIsAutomatedRetry Then Manager.OnNodeException(Me, ex)
        Finally
            Close()
            Manager.OnNodeStatusChanged(Me)
        End Try
    End Sub

    Private Function CreateAndConnectDataChannel() As Boolean
        mClientData = New TcpClient
        mClientData.Connect(mBuddy.Host, mBuddy.Port)
        mClientDataStream = New ThrottledNetworkStream(mClientData.GetStream)
        SendCommandLine(mClientData, "TYPE:DATACHANNEL")

        SendCommandLine(mClientData, "PASSCODE:" & mOneTimeDataChannelPassCode)

        Dim strReply As String = ReadCommandLine(mClientData, 30)
        If strReply <> "OK" Then Return False

        mOneTimeDataChannelPassCode = Nothing

        strReply = ReadCommandLine(mClientCommands, 30)
        If strReply <> "DATACHANNELCONNECTED" Then Return False

        Manager.OnNodeLogEvent(Me, "Datachannel connected")
        Return True
    End Function

    Private Sub WorkerPairUp(ByVal parameter As Object)
        Try
            Dim clsPairUpInfo As PairUpInfo = CType(parameter, PairUpInfo)

            mClientCommands = New TcpClient

            Manager.OnNodeStatusChanged(Me)

            mClientCommands.Connect(clsPairUpInfo.Host, clsPairUpInfo.Port)

            SendCommandLine(mClientCommands, "TYPE:COMMANDCHANNEL")

            If Not NegotiateEncryption(False) Then Return

            'now, send the pair up command block
            Dim clsArgs As New CommandBlocks.PairUp.Request
            clsArgs.BuddyId = clsPairUpInfo.BuddyNr
            clsArgs.PinCode = clsPairUpInfo.PinCode
            clsArgs.Host = Framework.Net.Settings.Host
            clsArgs.Port = Framework.Net.Settings.Port
            clsArgs.Name = Framework.Net.Settings.Nickname
            clsArgs.Email = Framework.Net.Settings.Email
            clsArgs.PassCode = Framework.Misc.GeneratePassCode(64)
            clsArgs.UniqueId = Framework.Net.Settings.UniqueId

            PostCommandMessage("PAIRUP", Framework.Serialization.ToXmlBytes(clsArgs))

            Dim clsReplyCommand As MessageBlock = ReadMessageBlock(mClientCommands, 30)
            If clsReplyCommand.Type <> "PAIRUPREPLY" Then Return
            If Not CommandBlocks.PairUp.HandleReply(Me, clsPairUpInfo, clsArgs, clsReplyCommand) Then Return
            Manager.OnNodeLogEvent(Me, String.Format("Pairup with buddy {0} complete", mBuddy.NameLocal))

            If Not CreateAndConnectDataChannel() Then Return

            SendCommandLine("ENTERCOMMANDLOOP")

            'start data loop
            StartDataLoop()
            'now, go into the command loop
            CommandLoop()
        Catch ex As System.Threading.ThreadAbortException
        Catch ex As Exception
            Manager.OnNodeException(Me, ex)
        Finally
            Close()
            Manager.OnNodeStatusChanged(Me)
        End Try
    End Sub

    Private mIsDisconnecting As Boolean = False

    Private Sub StartDataLoop()
        mDataReadThread = New System.Threading.Thread(AddressOf DataReadLoop)
        mDataReadThread.Name = "Data Read Thread"
        mDataReadThread.Priority = System.Threading.ThreadPriority.BelowNormal
        mDataReadThread.IsBackground = True
        mDataReadThread.Start()

        mDataSendThread = New System.Threading.Thread(AddressOf DataSendLoop)
        mDataSendThread.Name = "Data Read Thread"
        mDataSendThread.Priority = System.Threading.ThreadPriority.BelowNormal
        mDataSendThread.IsBackground = True
        mDataSendThread.Start()

    End Sub
    Private Sub DataReadLoop()
        Manager.OnNodeLogEvent(Me, "Data read loop started")
        Try
            Do
                Try
                    Dim clsData As IncomingDataChannelBlock = ReadDataChannelBlock(-1)
                    If clsData Is Nothing Then Exit Do
                    Manager.BlockArrived(clsData.ChannelName, Me, clsData)
                Catch ex As System.Threading.ThreadAbortException
                    Exit Do
                Catch ex As Exception
                    Manager.OnNodeException(Me, ex)
                End Try
            Loop
        Catch ex As System.Threading.ThreadAbortException
        Catch ex As Exception
            Manager.OnNodeException(Me, ex)
        End Try
    End Sub
    Private Sub DataSendLoop()
        Manager.OnNodeLogEvent(Me, "Data send loop started")
        Try
            Do
                Try
                    Dim clsBlock As OutgoingDataChannelBlock = Nothing
                    Dim strChannel As String = Nothing
                    clsBlock = Manager.GetNextDataBlock(Me, strChannel)
                    If clsBlock IsNot Nothing Then
                        WriteDataChannelBlockToStream(strChannel, clsBlock)
                        Manager.BlockSent(Me, strChannel, clsBlock)
                    Else
                        System.Threading.Thread.Sleep(1000)
                    End If
                Catch ex As System.Threading.ThreadAbortException
                    Exit Do
                Catch ex As Exception
                    Manager.OnNodeException(Me, ex)
                End Try
            Loop
        Catch ex As System.Threading.ThreadAbortException
        Catch ex As Exception
            Manager.OnNodeException(Me, ex)
        End Try
    End Sub

    Private Sub CommandLoop()
        mIsOnline = True
        Manager.OnNodeStatusChanged(Me)
        Manager.OnNodeLogEvent(Me, "Going into command loop")
        Try
            Do
                Dim clsCmd As MessageBlock = ReadMessageBlock(mClientCommands, -1)
                If clsCmd Is Nothing Then Exit Do
                Select Case clsCmd.Type
                    Case "BYE"
                        mIsDisconnecting = True
                        Manager.OnNodeStatusChanged(Me)
                        PostCommandMessage("BYEBYE", Nothing)
                        Exit Do
                    Case "BYEBYE"
                        mIsDisconnecting = True
                        Manager.OnNodeStatusChanged(Me)
                        Exit Do
                    Case "COMMAND", "COMMANDREPLY"
                        OnCommandMessage(clsCmd)
                    Case Else
                        'Manager.OnNodeLogEvent(Me, String.Format("Command {0} ignored", clsCmd.Message))
                End Select
            Loop
        Finally
            mIsOnline = False
        End Try
    End Sub

    Private Sub OnCommandMessage(ByVal message As MessageBlock)
        Dim clsEnvelope As New MessageEnvelope(Me, message)
        If message.Type = "COMMANDREPLY" Then
            If mMessageHooks.ContainsKey(message.InReplyToId) Then
                mMessageReplies.Add(message.InReplyToId, message)
                mMessageHooks(message.InReplyToId).Set()
                mMessageHooks.Remove(message.InReplyToId)
                Return
            End If
        End If
        Manager.OnCommandMessage(clsEnvelope)
    End Sub

    Private mMessageHooks As New Generic.Dictionary(Of Integer, System.Threading.ManualResetEvent)
    Private mMessageReplies As New Generic.Dictionary(Of Integer, MessageBlock)

    Private Function GetReplyHook(ByVal message As MessageBlock) As System.Threading.ManualResetEvent
        Dim clsHook As New System.Threading.ManualResetEvent(False)
        SyncLock mMessageHooks
            mMessageHooks.Add(message.Id, clsHook)
        End SyncLock
        Return clsHook
    End Function

    Friend Sub PostCommandMessage(ByVal type As String, ByVal message As Byte())
        Dim clsMessage As New MessageBlock
        clsMessage.Id = Manager.GetNextMessageId()
        clsMessage.Type = type
        clsMessage.Message = message
        SendMessageBlock(mClientCommands, clsMessage)
    End Sub

    ''' <summary>
    ''' Posts message and returns the MessageId
    ''' </summary>
    ''' <param name="commandCode"></param>
    ''' <param name="message"></param>
    ''' <returns>MessageId</returns>
    ''' <remarks></remarks>
    Public Function SendCommandMessageAsync(ByVal commandCode As String, ByVal message As Byte()) As Integer
        Dim clsMessage As New MessageBlock
        clsMessage.Id = Manager.GetNextMessageId()
        clsMessage.Type = "COMMAND"
        clsMessage.Code = commandCode
        clsMessage.Message = message
        SendMessageBlock(mClientCommands, clsMessage)
        Return clsMessage.Id
    End Function
    Public Sub SendCommandMessageAsync(ByVal messageId As Integer, ByVal commandCode As String, ByVal message As Byte())
        Dim clsMessage As New MessageBlock
        clsMessage.Id = messageId
        clsMessage.Type = "COMMAND"
        clsMessage.Code = commandCode
        clsMessage.Message = message
        SendMessageBlock(mClientCommands, clsMessage)
    End Sub

    Public Function SendReplyCommandMessageAsync(ByVal requestEnvelope As MessageEnvelope, ByVal commandCode As String, ByVal message As Byte()) As Integer
        Dim clsMessage As New MessageBlock
        clsMessage.Id = Manager.GetNextMessageId()
        clsMessage.InReplyToId = requestEnvelope.Message.Id
        clsMessage.Type = "COMMANDREPLY"
        clsMessage.Code = commandCode
        clsMessage.Message = message
        SendMessageBlock(mClientCommands, clsMessage)
        Return clsMessage.Id
    End Function

    ''' <summary>
    ''' Sends command message and waits for the reply
    ''' </summary>
    ''' <param name="commandCode"></param>
    ''' <param name="message"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function SendCommandMessage(ByVal commandCode As String, ByVal message As Byte()) As MessageBlock
        Dim clsMessage As New MessageBlock
        clsMessage.Id = Manager.GetNextMessageId()
        clsMessage.Type = "COMMAND"
        clsMessage.Code = commandCode
        clsMessage.Message = message
        'clsMessage.BinaryData = Nothing
        Dim clsHook As System.Threading.ManualResetEvent = GetReplyHook(clsMessage)

        SendMessageBlock(mClientCommands, clsMessage)

        clsHook.WaitOne(30000, False)

        If mMessageReplies.ContainsKey(clsMessage.Id) Then
            Dim clsRet As MessageBlock = mMessageReplies.Item(clsMessage.Id)
            mMessageReplies.Remove(clsMessage.Id)
            Return clsRet
        Else
            Throw New Framework.Exceptions.TimedOutException("Did not get a reply from Buddy!")
        End If
    End Function

    'Private mDataBlockUploadQueue As New Generic.Queue(Of DataBlock)

    Friend Sub WriteDataChannelBlockToStream(ByVal channelName As String, ByVal block As OutgoingDataChannelBlock)
        Dim bHeader() As Byte
        Using clsMemIO As New System.IO.MemoryStream
            BlockStreamWriter.Write(clsMemIO, CShort(1)) 'protocol version
            BlockStreamWriter.Write(clsMemIO, channelName)
            BlockStreamWriter.Write(clsMemIO, block.ReferenceId)
            BlockStreamWriter.Write(clsMemIO, block.TrackingId)
            BlockStreamWriter.Write(clsMemIO, block.BytesToSend)
            bHeader = clsMemIO.ToArray
        End Using

        'first, send 4-byte header size encrypted  
        Dim bSize() As Byte = BitConverter.GetBytes(bHeader.Count)
        mDataSendCryptoProvider.Encrypt(bSize)
        mClientDataStream.Write(bSize, 0, bSize.Length)

        'now, encrypt and send header data
        mDataSendCryptoProvider.Encrypt(bHeader)
        mClientDataStream.Write(bHeader, 0, bHeader.Length)

        Dim clsCRC32 As New Framework.Hash.Crc32

        If block.DataStream IsNot Nothing Then
            Dim iSent As Integer = 0
            Dim bBuffer(1472 - 1) As Byte
            While iSent < block.BytesToSend
                'now, read from dataStream
                Dim iLenToRead As Integer = Math.Min(bBuffer.Count, block.BytesToSend - iSent)
                Dim iRead As Integer = block.DataStream.Read(bBuffer, 0, iLenToRead)
                'update crc32 with block just read
                clsCRC32.Update(bBuffer, 0, iRead)
                'and encrypt whatever we received
                mDataSendCryptoProvider.Encrypt(bBuffer, 0, iRead)
                'and send it to the networkstream
                mClientDataStream.Write(bBuffer, 0, iRead)
                iSent += iRead
            End While
        End If
        'now, get crc32, encrypt it and send it
        Dim bCRC32() As Byte = BitConverter.GetBytes(clsCRC32.Value)
        mDataSendCryptoProvider.Encrypt(bCRC32)
        mClientDataStream.Write(bCRC32, 0, bCRC32.Length)

        If Settings.TraceTraffic Then
            Manager.OnNetworkTraceInfo(Me, channelName, block.ReferenceId & " - " & block.TrackingId, block.BytesToSend, Manager.NetworkDataPackage.PackageDirection.Outgoing)
        End If
    End Sub

    Private Function ReadEncryptedSizeFromStream(ByVal stream As System.IO.Stream, ByVal cryptoProvider As CryptoProviders.B2B1) As Integer
        Dim bDataSize() As Byte = BlockStreamReader.ReadRawBytes(stream, 4)
        cryptoProvider.Decrypt(bDataSize)
        Return BitConverter.ToInt32(bDataSize, 0)
    End Function

    Private Function ReadDataChannelBlock(Optional ByVal timeOut As Integer = -1) As IncomingDataChannelBlock
        If timeOut = -1 Then
            mClientData.GetStream.ReadTimeout = timeOut
        Else
            mClientData.GetStream.ReadTimeout = timeOut * 1000
        End If

        'read and decrypt the header
        Dim iHeaderSize As Integer = ReadEncryptedSizeFromStream(mClientDataStream, mDataReceiveCryptoProvider)

        'now read and decrypt header
        Dim bHeader() As Byte
        bHeader = BlockStreamReader.ReadRawBytes(mClientDataStream, iHeaderSize)
        mDataReceiveCryptoProvider.Decrypt(bHeader)

        Dim clsRet As New IncomingDataChannelBlock
        Dim iVersion As Short
        Dim iDataSize As Integer
        Using clsMS As New System.IO.MemoryStream(bHeader)
            iVersion = BlockStreamReader.ReadShort(clsMS)
            clsRet.ChannelName = BlockStreamReader.ReadString(clsMS)
            clsRet.ReferenceId = BlockStreamReader.ReadString(clsMS)
            clsRet.TrackingId = BlockStreamReader.ReadString(clsMS)
            iDataSize = BlockStreamReader.ReadInteger(clsMS)
        End Using

        Dim clsBuffer As System.IO.Stream = Manager.GetStreamForIncomingBlock(Me, clsRet.ChannelName, clsRet.ReferenceId)
        mClientDataStream.SetActiveBlock(clsRet)

        Dim clsCRC32 As New Framework.Hash.Crc32

        'now read and decrypt data
        Dim iSegmentSize As Integer = 1472
        Dim bBuffer(iSegmentSize - 1) As Byte
        For i As Integer = 0 To iDataSize - 1 Step iSegmentSize
            Dim iLen As Integer
            If i + iSegmentSize > iDataSize Then
                iLen = iDataSize - i
            Else
                iLen = iSegmentSize
            End If
            'fill buffer from network stream
            BlockStreamReader.ReadRawBytes(mClientDataStream, bBuffer, 0, iLen)
            'decrypt data
            mDataReceiveCryptoProvider.Decrypt(bBuffer, 0, iLen)
            'update crc32
            clsCRC32.Update(bBuffer, 0, iLen)
            'write to stream
            clsBuffer.Write(bBuffer, 0, iLen)
        Next
        'now, next 4 bytes is expected encrypted crc32 of data
        Dim bCRC32(4 - 1) As Byte
        BlockStreamReader.ReadRawBytes(mClientDataStream, bCRC32, 0, 4)
        mDataReceiveCryptoProvider.Decrypt(bCRC32)
        Dim uiCRC32 As UInteger = BitConverter.ToUInt32(bCRC32, 0)
        If uiCRC32 <> clsCRC32.Value Then
            clsRet.Crc32CheckFailed = True
        Else
            clsRet.Crc32CheckFailed = False
        End If

        mClientDataStream.ClearActiveBlock()

        clsRet.BytesRead = iDataSize

        If Settings.TraceTraffic Then
            Manager.OnNetworkTraceInfo(Me, clsRet.ChannelName, clsRet.ReferenceId & " - " & clsRet.TrackingId, iDataSize, Manager.NetworkDataPackage.PackageDirection.Incoming)
        End If
        Return clsRet
    End Function

    Private Sub SendMessageBlock(ByVal client As TcpClient, ByVal message As MessageBlock)
        If Settings.TraceTraffic Then Manager.OnNetworkTraceInfo(Me, message.Code, message.Message, Manager.NetworkDataPackage.PackageDirection.Outgoing)

        Dim bMessageBlock() As Byte
        Using clsMemStream As New System.IO.MemoryStream()
            'write blockversion
            BlockStreamWriter.Write(clsMemStream, CShort(1))

            BlockStreamWriter.Write(clsMemStream, message.Id)
            BlockStreamWriter.Write(clsMemStream, message.InReplyToId)
            BlockStreamWriter.Write(clsMemStream, message.Type)
            BlockStreamWriter.Write(clsMemStream, message.Message)
            BlockStreamWriter.Write(clsMemStream, message.Code)

            bMessageBlock = clsMemStream.ToArray
        End Using

        'log block, if active...
        'If Settings.TraceTraffic Then Manager.OnNetworkTraceInfo(Me, message.Code, bMessageBlock, Manager.NetworkDataPackage.PackageDirection.Outgoing)

        mCommandSendCryptoProvider.Encrypt(bMessageBlock)

        'now, initiate async send
        BlockStreamWriter.Write(client.GetStream, bMessageBlock)

    End Sub
    Private Function ReadMessageBlock(ByVal client As TcpClient, ByVal timeOut As Integer) As MessageBlock
        If timeOut = -1 Then
            client.GetStream.ReadTimeout = timeOut
        Else
            client.GetStream.ReadTimeout = timeOut * 1000
        End If

        Dim bPayload() As Byte = BlockStreamReader.ReadBytes(client.GetStream)
        'If Settings.TraceTraffic Then Manager.OnNetworkTraceInfo(Me, String.Empty, bPayload, Manager.NetworkDataPackage.PackageDirection.Incoming)

        mCommandReceiveCryptoProvider.Decrypt(bPayload)

        Dim clsRet As New MessageBlock
        Using clsMS As New System.IO.MemoryStream(bPayload)
            clsRet.Version = BlockStreamReader.ReadShort(clsMS)
            clsRet.Id = BlockStreamReader.ReadInteger(clsMS)
            clsRet.InReplyToId = BlockStreamReader.ReadInteger(clsMS)
            clsRet.Type = BlockStreamReader.ReadString(clsMS)
            clsRet.Message = BlockStreamReader.ReadBytes(clsMS)
            clsRet.Code = BlockStreamReader.ReadString(clsMS)
        End Using
        If Settings.TraceTraffic Then Manager.OnNetworkTraceInfo(Me, clsRet.Code, clsRet.Message, Manager.NetworkDataPackage.PackageDirection.Incoming)
        Return clsRet
    End Function

    Private mNetworkId As String
    Public ReadOnly Property NetworkId() As String
        Get
            Return mNetworkId
        End Get
    End Property

    Friend Sub SendCommandLine(ByVal text As String)
        SendCommandLine(mClientCommands, text)
    End Sub
    Friend Sub SendCommandLine(ByVal client As TcpClient, ByVal text As String)
        Dim bBytes() As Byte = System.Text.Encoding.UTF8.GetBytes(text & vbNewLine)
        Manager.OnNetworkTraceInfo(Me, String.Empty, bBytes, Manager.NetworkDataPackage.PackageDirection.Outgoing)
        client.GetStream.Write(bBytes, 0, bBytes.Length)
    End Sub

    Friend Sub SetDataChannel(ByVal client As TcpClient)
        mClientData = client
        mClientDataStream = New ThrottledNetworkStream(mClientData.GetStream)
    End Sub

    Friend Sub CloseAndAbort()
        Try
            If mDataReadThread IsNot Nothing Then mDataReadThread.Abort()
        Catch
        End Try
        Try
            If mDataSendThread IsNot Nothing Then mDataSendThread.Abort()
        Catch
        End Try
        Try
            If mCommandReadThread IsNot Nothing Then mCommandReadThread.Abort()
        Catch
        End Try
        Close()
    End Sub
    Friend Sub Close()
        If Not mIsDisconnecting Then
            Try
                If mClientCommands IsNot Nothing Then mClientCommands.Client.Close()
            Catch
            End Try
            Try
                If mClientData IsNot Nothing Then mClientData.Client.Close()
            Catch
            End Try
        End If
        Try
            If mDataReadThread IsNot Nothing Then mDataReadThread.Abort()
        Catch
        End Try
        Try
            If mDataSendThread IsNot Nothing Then mDataSendThread.Abort()
        Catch
        End Try
        Manager.RemoveNode(Me)
    End Sub
    Friend Sub Disconnect()
        If mIsOnline Then PostCommandMessage("BYE", Nothing)
    End Sub

    Friend Sub StartServer(ByVal client As TcpClient)
        mClientCommands = client
        mNetworkId = client.Client.RemoteEndPoint.ToString

        mCommandReadThread = New System.Threading.Thread(AddressOf Me.WorkerServer)
        mCommandReadThread.Name = "NetNode.Node server command read thread for " & mNetworkId
        mCommandReadThread.Priority = System.Threading.ThreadPriority.BelowNormal
        mCommandReadThread.IsBackground = True
        mCommandReadThread.Start()
    End Sub

    Private Sub WorkerServer()
        Try
            Manager.OnNodeStatusChanged(Me)

            'first, get the type of connection
            Dim strConnectionType As String = ReadCommandLine(30)
            Select Case strConnectionType
                Case "TYPE:COMMANDCHANNEL"
                    'now, negotiate encryption
                    If Not NegotiateEncryption(True) Then Return

                    'now, validate that the client is a valid buddy
                    Dim clsCmdBlock As MessageBlock = ReadMessageBlock(mClientCommands, 30)
                    Select Case clsCmdBlock.Type
                        Case "PAIRUP"
                            If Not CommandBlocks.PairUp.HandleRequest(Me, clsCmdBlock) Then Return
                        Case "AUTHENTICATE"
                            If Not CommandBlocks.Authenticate.HandleRequest(Me, clsCmdBlock) Then Return
                        Case Else 'invalid command, exit and close
                            Return
                    End Select

                    Dim strReply As String = ReadCommandLine(30)
                    If strReply <> "ENTERCOMMANDLOOP" Then Return

                    'start data loop
                    StartDataLoop()
                    'now, go into the command loop
                    CommandLoop()
                Case "TYPE:DATACHANNEL"
                    'read onetimepasscode
                    Dim strPassCode As String = ReadCommandLine(30)
                    If Not strPassCode.StartsWith("PASSCODE:") Then Return
                    strPassCode = strPassCode.Substring(9)
                    'now, find client with correct passcode
                    Dim clsNode As Node = Manager.GetNodeByOneTimeDataChannelPassCode(strPassCode)
                    If clsNode Is Nothing Then Return
                    clsNode.SetDataChannel(mClientCommands)
                    SendCommandLine("OK")
                    mOneTimeDataChannelPassCode = Nothing
                    clsNode.SendCommandLine("DATACHANNELCONNECTED")
                    mClientCommands = Nothing
                    Return
                Case Else
                    Return
            End Select
        Catch ex As System.Threading.ThreadAbortException
        Catch ex As Exception
            Manager.OnNodeException(Me, ex)
        Finally
            Close()
            If mClientCommands IsNot Nothing Then Manager.OnNodeStatusChanged(Me)
        End Try
    End Sub

    Private Const mPublicRsaKeyPrefix As String = "PUBLICRSAKEY:"

    Private Function NegotiateEncryption(ByVal actAsServer As Boolean) As Boolean
        'Manager.OnNodeLogEvent(Me, "Encryption negotiation started")
        If actAsServer Then
            'since we're the "server" beeing called, we start the conversation by sending
            'the caller our public RSA key
            mMyASymCryptoProvider = New System.Security.Cryptography.RSACryptoServiceProvider(1024)
            SendCommandLine(mPublicRsaKeyPrefix & mMyASymCryptoProvider.ToXmlString(False))

            'now, read the buddy's public RSA Key
            Dim strBuddyRSAKey As String = ReadCommandLine(30)
            If Not strBuddyRSAKey.ToUpper.StartsWith(mPublicRsaKeyPrefix) Then Return False
            strBuddyRSAKey = strBuddyRSAKey.Substring(mPublicRsaKeyPrefix.Length)
            mBuddyASymCryptoProvider = New System.Security.Cryptography.RSACryptoServiceProvider(1024)
            mBuddyASymCryptoProvider.FromXmlString(strBuddyRSAKey)

            'now exchange crypto seeds 
            'create my crypto that will be used when receiving data
            mCommandReceiveCryptoProvider = New CryptoProviders.B2B1
            Dim aReceiveSeed As Generic.List(Of Byte()) = mCommandReceiveCryptoProvider.GetSeed
            mDataReceiveCryptoProvider = New CryptoProviders.B2B1(aReceiveSeed)
            If Settings.TraceTraffic Then
                Manager.OnNetworkTraceInfo(Me, String.Format("My Cryptyo Key : {0}", Framework.Misc.ToHexStringNice(mCommandReceiveCryptoProvider.Key)))
                Manager.OnNetworkTraceInfo(Me, String.Format("My Cryptyo IV  : {0}", mCommandReceiveCryptoProvider.IV))
            End If
            SendCommandLine("PROVIDER:B2B1")
            Dim clsSB As New System.Text.StringBuilder("SEED:")
            For Each aReceiveSeedLine As Byte() In aReceiveSeed
                Dim aReceiveSeedLineEncrypted() As Byte = mBuddyASymCryptoProvider.Encrypt(aReceiveSeedLine, False)
                Dim strReceiveSeedLineEncrypted As String = System.Convert.ToBase64String(aReceiveSeedLineEncrypted)
                clsSB.Append(strReceiveSeedLineEncrypted)
                clsSB.Append(" "c)

                If Settings.TraceTraffic Then
                    Manager.OnNetworkTraceInfo(Me, String.Format("My Seed Line Raw : {0}", Framework.Misc.ToHexStringNice(aReceiveSeedLine)))
                    Manager.OnNetworkTraceInfo(Me, String.Format("My Seed Line Encr: {0}", Framework.Misc.ToHexStringNice(aReceiveSeedLineEncrypted)))
                    Manager.OnNetworkTraceInfo(Me, String.Format("My Seed Line Str : {0}", strReceiveSeedLineEncrypted))
                End If
            Next
            SendCommandLine(clsSB.ToString.Trim)

            ''now, wait for a reply with buddys symmetric encryption parameters to use when sending data
            Dim strSendMethod As String = ReadCommandLine(30)
            If Not strSendMethod.StartsWith("PROVIDER:") Then Return False
            strSendMethod = strSendMethod.Substring(9)
            Select Case strSendMethod.ToUpper
                Case "B2B1"
                    Dim strSendSeed As String = ReadCommandLine(30)
                    If Not strSendSeed.StartsWith("SEED:") Then Return False
                    Dim aSendSeedEncrypted() As String = strSendSeed.Substring(5).Split(" "c)
                    Dim aSendSeed As New Generic.List(Of Byte())
                    For Each strSendSeedLine As String In aSendSeedEncrypted
                        Dim aSendSeedLineEncrypted As Byte() = System.Convert.FromBase64String(strSendSeedLine)
                        Dim aSendSeedLine As Byte() = mMyASymCryptoProvider.Decrypt(aSendSeedLineEncrypted, False)
                        aSendSeed.Add(aSendSeedLine)
                        If Settings.TraceTraffic Then
                            Manager.OnNetworkTraceInfo(Me, String.Format("Buddy's Seed Line Str : {0}", strSendSeedLine))
                            Manager.OnNetworkTraceInfo(Me, String.Format("Buddy's Seed Line Encr: {0}", Framework.Misc.ToHexStringNice(aSendSeedLineEncrypted)))
                            Manager.OnNetworkTraceInfo(Me, String.Format("Buddy's Seed Line Raw : {0}", Framework.Misc.ToHexStringNice(aSendSeedLine)))
                        End If
                    Next
                    mCommandSendCryptoProvider = New CryptoProviders.B2B1(aSendSeed)
                    mDataSendCryptoProvider = New CryptoProviders.B2B1(aSendSeed)
                    If Settings.TraceTraffic Then
                        Manager.OnNetworkTraceInfo(Me, String.Format("Buddy's Cryptyo Key : {0}", Framework.Misc.ToHexStringNice(mCommandSendCryptoProvider.Key)))
                        Manager.OnNetworkTraceInfo(Me, String.Format("Buddy's Cryptyo IV  : {0}", mCommandSendCryptoProvider.IV))
                    End If
                Case Else
                    Return False
            End Select

            'now, we're all set. Send READY response
            SendCommandLine("READY")
        Else
            'we're the "client"
            'read lines first line that cointains the RSAKEY
            Dim strLine As String = ReadCommandLine(30)
            If Not strLine.ToUpper.StartsWith(mPublicRsaKeyPrefix) Then Return False
            strLine = strLine.Substring(mPublicRsaKeyPrefix.Length)
            'initialize our rsa provider with the public key sent
            mBuddyASymCryptoProvider = New System.Security.Cryptography.RSACryptoServiceProvider(1024)
            mBuddyASymCryptoProvider.FromXmlString(strLine)

            'now create send our asymmetric public key
            mMyASymCryptoProvider = New System.Security.Cryptography.RSACryptoServiceProvider(1024)
            SendCommandLine(mPublicRsaKeyPrefix & mMyASymCryptoProvider.ToXmlString(False))

            'now exchange crypto seeds
            'now, wait for a reply with buddys symmetric encryption parameters
            Dim strSendMethod As String = ReadCommandLine(30)
            If Not strSendMethod.StartsWith("PROVIDER:") Then Return False
            strSendMethod = strSendMethod.Substring(9)
            Select Case strSendMethod.ToUpper
                Case "B2B1"
                    Dim strSendSeed As String = ReadCommandLine(30)
                    If Not strSendSeed.StartsWith("SEED:") Then Return False
                    Dim aSendSeedEncrypted() As String = strSendSeed.Substring(5).Split(" "c)
                    Dim aSendSeed As New Generic.List(Of Byte())
                    For Each strSendSeedLine As String In aSendSeedEncrypted
                        Dim aSendSeedLineEncrypted As Byte() = System.Convert.FromBase64String(strSendSeedLine)
                        Dim aSendSeedLine As Byte() = mMyASymCryptoProvider.Decrypt(aSendSeedLineEncrypted, False)
                        aSendSeed.Add(aSendSeedLine)
                        If Settings.TraceTraffic Then
                            Manager.OnNetworkTraceInfo(Me, String.Format("Buddy's Seed Line Str : {0}", strSendSeedLine))
                            Manager.OnNetworkTraceInfo(Me, String.Format("Buddy's Seed Line Encr: {0}", Framework.Misc.ToHexStringNice(aSendSeedLineEncrypted)))
                            Manager.OnNetworkTraceInfo(Me, String.Format("Buddy's Seed Line Raw : {0}", Framework.Misc.ToHexStringNice(aSendSeedLine)))
                        End If
                    Next
                    mCommandSendCryptoProvider = New CryptoProviders.B2B1(aSendSeed)
                    mDataSendCryptoProvider = New CryptoProviders.B2B1(aSendSeed)
                    If Settings.TraceTraffic Then
                        Manager.OnNetworkTraceInfo(Me, String.Format("Buddy's Cryptyo Key : {0}", Framework.Misc.ToHexStringNice(mCommandSendCryptoProvider.Key)))
                        Manager.OnNetworkTraceInfo(Me, String.Format("Buddy's Cryptyo IV  : {0}", mCommandSendCryptoProvider.IV))
                    End If
                Case Else
                    Return False
            End Select

            mCommandReceiveCryptoProvider = New CryptoProviders.B2B1
            Dim aReceiveSeed As Generic.List(Of Byte()) = mCommandReceiveCryptoProvider.GetSeed
            mDataReceiveCryptoProvider = New CryptoProviders.B2B1(aReceiveSeed)
            'Manager.OnNetworkTraceInfo(mClientCommands, String.Format("My Cryptyo Seed: {0}", Framework.Misc.ToHexStringNice(aReceiveSeed)))
            Manager.OnNetworkTraceInfo(Me, String.Format("My Cryptyo Key : {0}", Framework.Misc.ToHexStringNice(mCommandReceiveCryptoProvider.Key)))
            Manager.OnNetworkTraceInfo(Me, String.Format("My Cryptyo IV  : {0}", mCommandReceiveCryptoProvider.IV))
            SendCommandLine("PROVIDER:B2B1")
            Dim clsSB As New System.Text.StringBuilder("SEED:")
            For Each aReceiveSeedLine As Byte() In aReceiveSeed
                Dim aReceiveSeedLineEncrypted() As Byte = mBuddyASymCryptoProvider.Encrypt(aReceiveSeedLine, False)
                Dim strReceiveSeedLineEncrypted As String = System.Convert.ToBase64String(aReceiveSeedLineEncrypted)
                clsSB.Append(strReceiveSeedLineEncrypted)
                clsSB.Append(" "c)
                If Settings.TraceTraffic Then
                    Manager.OnNetworkTraceInfo(Me, String.Format("My Seed Line Raw : {0}", Framework.Misc.ToHexStringNice(aReceiveSeedLine)))
                    Manager.OnNetworkTraceInfo(Me, String.Format("My Seed Line Encr: {0}", Framework.Misc.ToHexStringNice(aReceiveSeedLineEncrypted)))
                    Manager.OnNetworkTraceInfo(Me, String.Format("My Seed Line Str : {0}", strReceiveSeedLineEncrypted))
                End If
            Next
            SendCommandLine(clsSB.ToString.Trim)

            strLine = ReadCommandLine(30)
            'response should be "READY"
            Return strLine = "READY"
        End If
        Return True
    End Function

    Friend Function ReadCommandLine(ByVal timeout As Integer) As String
        Return ReadCommandLine(mClientCommands, timeout)
    End Function
    Friend Function ReadCommandLine(ByVal client As TcpClient, ByVal timeout As Integer) As String
        If timeout = -1 Then
            client.GetStream.ReadTimeout = -1
        Else
            client.GetStream.ReadTimeout = timeout * 1000
        End If
        Dim bBuffer(0) As Byte
        Dim bLast As Byte
        Dim bReturn As New Generic.List(Of Byte)
        Do
            Dim iReadBytes As Integer = client.GetStream.Read(bBuffer, 0, 1)
            If iReadBytes <> 1 Then Return Nothing
            If bBuffer(0) = 10 AndAlso bLast = 13 Then
                Exit Do
            ElseIf bLast <> 0 Then
                bReturn.Add(bLast)
            End If
            bLast = bBuffer(0)
        Loop
        Dim bData() As Byte = bReturn.ToArray
        Dim strRet As String = System.Text.Encoding.UTF8.GetString(bData)
        If Settings.TraceTraffic Then Manager.OnNetworkTraceInfo(Me, String.Empty, bData, Manager.NetworkDataPackage.PackageDirection.Incoming)
        Return strRet
    End Function




End Class
